//
// Created by Administrator on 2024/6/13.
//
#include "stdio.h"
#include "stdlib.h"
/**
 * 所谓“文件”一般是指存储在外部介质上数据的集合。操作系统是以文件为单位对数据进行管理的，按文件名进行存取。从操作系统的角度看，用于输入的键盘和用于输出的显示器和打印机也分别相当于一个文件。
根据文件中数据的组织形式，可以将文件分为ASCII文件和二进制文件。ASCII文件又称为文本文件，西文主要由ASCII码字符组成，中文主要由机内码组成，如*.txt文件、源程序文件等，Windows环境中可以通过记事本打开查看并编辑。二进制文件主要由二进制数组成，如*.exe文件、*.dat文件等，一般通过专用软件打开查看并编辑，若用记事本打开看到的一般是乱码(参见12.4.2节)。在C语言Windows环境中，其本质上的区别是，文本文件将换行符转换成回车换行进行存储(Linux中无此转换)，而二进制文件没有此转换。
需要说明的是，对于一个数，按数值存储和按字符存储，其结果有很大的区别。如，若将16706按数值存储(这也是内存单元中的存储形式)，则TC下要占2个字节，按十六进制显示为“42 41”(16706对应的十六进制数为0x4142)，以记事本打开看到的是“BA”；若将其按字符存储(存储前要将数值形式转换成字符形式)，则要占5个字节，按十六进制显示为“31 36 37 30 36”(相当于字符串"16706")，以记事本打开看到的是“16706”。
一般情况下，数值按数值形式存储于二进制文件，字符串按字符形式存储于文本文件。这样，节省空间，效率又高。若TC下有如下定义，变量i的值按数值形式存储占2个字节，按字符形式存储占5个字节；而含80个元素的字符数组s的值按数值形式存储占80个字节，按字符形式存储占1个字节。
int i=10000;
char s[80]="A";
 */
/**
 * 文件类型指针
 * 为操作文件，必须定义一个结构体类型变量，以存放与文件读写相关的信息，如文件名、文件状态、文件当前位置等。该结构体类型就是定义在stdio.h头文件中的FILE，其声明如下：
 */
//typedef struct  {
//    short           level;          /* 缓冲区满或空程度 */
//    unsigned        flags;          /* 文件状态标志     */
//    char            fd;             /* 文件描述符       */
//    unsigned char   hold;           /* 如无缓冲区不读取字符 */
//    short           bsize;          /* 缓冲区的大小         */
//    unsigned char   *buffer;        /* 数据传输缓冲区       */
//    unsigned char   *curp;          /* 当前位置指针 */
//    unsigned        istemp;         /* 临时文件指示器 */
//    short           token;          /* 用于有效性检查 */
//}       FILE;
//FILE类型结构体指针变量的定义如下：
//FILE *fp;
//系统预定义了五个标准文件指针变量，用于实现对标准设备的读或写，它们是：
//stdin:标准输入(键盘)
//stdout:标准输出(显示器)
//stderr:错误输出(显示器)
//stdprn:打印机
//stdaux:指向空

/**
 * 步骤
 * (1)定义文件指针变量并以指定的打开方式打开指定的文件；
 * (2)以文件指针变量作参数调用相应函数读或写文件；
 * (3)以文件指针变量作参数关闭相应文件。
 * r: 只读【为读打开一个文本文件】
 * w: 只写【为写新建一个文本文件】
 * a: 追加【从尾追加打开或新建一个文本文件】
 *
 * a+:读写【读写：从尾读写打开或新建一个文本文件】
 * w+:读写【从头读写新建一个文本文件】
 * r+:读写【从头读写打开一个文本文件】
 *
 * rb: 只读
 * wb: 只写
 * ab: 追加
 *
 * rb+: 读写
 * wb+: 读写
 * ab+: 读写
 *
 */
//对a.txt文件以"w"(只写) 或"w+"(读写)方式打开并写入后，分析其结果
void test_file_w()
{
    FILE *fp = fopen("a.txt", "w");//以只写方式打开文件
    fprintf(fp, "%s", "123"); //将字符“123”写入fp指定文件
    fclose(fp); //关闭fp指定文件
}

//对a.txt文件以"a"(只写)或"a+"(读写)方式打开并写入后，分析其结果
void test_file_a()
{
    FILE *fp = fopen("a.txt", "a");
    fprintf(fp, "%s", "abc");
    fclose(fp);
}

//对a.txt文件以"r+"(读写)方式打开并写入后，分析其结果
void test_file_r_plus()
{
    FILE *fp = fopen("a.txt", "r+");
    fprintf(fp, "%s", "asd"); //从头开始写
    fclose(fp);
}

//分析字符串“a\nb”以文本文件方式("w")写和二进制文件方式("wb")写的区别
void test_file_w_wb()
{
    FILE *fp = fopen("a.txt", "wb"); //以w写txt,4 bit  以wb则是3 bit
    fprintf(fp, "%s", "a\nb");
    fclose(fp);
}
//文件的读写函数
/**
 * 读写一字符fgetc()和fputc()
 * 1.从fp指定文件中读一个字符函数：int fgetc(fp)或int getc(fp)
说明：若成功读取，则返回读取的字符，否则返回EOF。
2.向fp指定文件写一个字符c：int fputc(c,fp);或int putc(c,fp);
说明：
(1)c是要输出的字符，可以是一个字符常量或字符变量。
(2)fp是文件指针变量。fputc( )函数执行成功，则返回字符值；若失败，则返回一个EOF(-1)。
(3)第4章介绍过putchar( )函数，其实是从fputc函数派生出来的，是fp=stdout时的特例。它是在stdio.h文件中预定义为：
#define putchar(c) fputc(c,stdout)
3.判断是否文件尾feof(fp)
说明：若fp指定文件读到文件尾，则函数feof(fp)返回的值为1(真)，否则为0(假)。
 */
//将文件a1.txt中内容复制到a2.txt中
void test_copy()
{
    FILE *f1, *f2;
    char c;
    f1 = fopen("a.txt", "r");//只读打开
    f2 = fopen("b.txt", "w");//只写打开
    while (!feof(f1))
    { //一个字符 一个字符复制
        c = fgetc(f1);//从f1读
        if (c == EOF)break; //EOF文件结束标记，则退出，不写到f2
        fputc(c, f2); //写入f2
    }
    fclose(f1);
    fclose(f2);
}

//将文件a1.txt中小写字母转换成大写字母转存到a2.txt。
void test_copy_2_uppercase()
{
    FILE *f1, *f2;
    char c;
    f1 = fopen("a1.txt", "r");
    f2 = fopen("a2.txt", "w");
    while (!feof(f1))
    {
        char c = getc(f1);
        if (c == EOF)break;
        if (c >= 'a' && c <= 'z')c = c - 32; //小写转大写
        fputc(c, f2);
    }
    fclose(f1);
    fclose(f2);
}

//读写数据fscanf()和fprintf()
/* fscanf(…)和fprintf(…)函数与scanf(…)和printf(…)函数功能相似，只是scanf(…)和printf(…)函数默认终端设备(键盘和显示器)，而fscanf(…)和fprintf(…)函数默认文件，而终端设备是文件的特例，具体格式如下。
1.fprintf( )函数
        格式：fprintf(fp,格式字符串,输出列表);
功能：把输出列表的值按格式字符串给定的格式写入fp指定文件中，若成功则返回实际写入的字符个数，否则返回负数。如，将变量x内容写入fp指定文件的格式如下：
fprintf(fp,"%d",x);
当fp=stdout时，fprintf(fp,…)的功能就相当于printf(…)。
2.fscanf( )函数
        格式：fscanf(fp,格式字符串,输入列表);
功能：按格式字符串指定的格式从fp指定文件中读取数据并赋值给输入列表中相应变量。若成功，则返回输入数据项个数；若遇文件尾，则返回EOF；若失败，则返回0。如，从fp指定文件中读入5个字符给字符串数组s的格式如下：
fscanf(fp,"%5s",s);
当fp=stdin时，fscanf(fp,…);的功能就相当于scanf(…);。
sscanf()和sprintf()函数有类似特点，如：
int i;float f;
sscanf("123  3.14","%d %f",&i,&f);*//*从"123  3.14"串中读,结果：i=123  f=3.14*//*
char s[80];sprintf(s,"x=%d",123); *//*输出到s串中,结果：s="x=123"*/

//用fscanf(…)和fprintf(…)函数实现将键盘输入内容存入test2.txt，且将小写字母转换成大写字母。
//todo 有问题（stdin)
void test_fscanf_fprintf_stdin()
{
    FILE *f1, *f2;
    char c = 0;
    //f1= fopen("a1.txt","r");
    f1 = stdin; /*终端设备键盘*/
    f2 = fopen("test2.txt", "w"); //为写创建一个文件
    while (!feof(f1))
    {
        fscanf(f1, "%c", &c);  /*从f1读一个字符*/
        //c= fgetc(f1);
        if (c == EOF)break;
        if (c >= 'a' && c <= 'z')c = c - 32;
        fprintf(f2, "%c", c);
    }
    fclose(f1);
    fclose(f2);
}

//用fscanf(…)和fprintf(…)函数实现将test1.txt内容送屏幕显示，且将小写字母转换成大写字母
//stdout可以
void test_fscanf_fprintf_stdout()
{
    FILE *f1, *f2;
    char c = 0;
    f1 = fopen("a1.txt", "r"); /*终端设备键盘*/
    //f1=stdin;
    //f2= fopen("test2.txt","w"); //为写创建一个文件
    f2 = stdout; /*显示器(屏幕)*/
    while (!feof(f1))
    {
        fscanf(f1, "%c", &c);  /*从f1读一个字符*/
        if (c == EOF)break;
        if (c >= 'a' && c <= 'z')c = c - 32;
        fprintf(f2, "%c", c); //写入f2,输出到屏幕
    }
    fclose(f1);
    fclose(f2);
}

/*
 * fgetc fputc
 * stdin stdout
 * fscanf
 读写一串fgets()和fputs()
fgets(…)和fputs(…)函数与gets(…)和puts(…)函数功能相似，只是gets(…)和puts(…)函数默认终端设备(键盘和显示器)，而fgets(…)和fputs(…)函数默认文件，而终端设备是文件的特例，具体格式如下：
1.fgets(…)函数
        格式：char *fgets(char *string, int n, FILE *stream);
功能：从stream指向的文件中读取n-1个字符，并把它们放到字符数组string中。若在读完n-1个字符之前遇到“\n”或EOF，读入即结束，同时在字符串后自动加上“\0”。操作成功返回值为string的首地址，否则返回空。如:fgets(s,10,fp);
2.fputs(…)函数
        格式：char *fputs(char *string,FILE *stream);
功能：把string字符串写入到stream指向的文件中，其中“\0”不写入。
如：fputs("How are you?",fp);
*/

// 向test.txt文件中写入三行文本：“How are you?”、“OK”、 “Good”。
void test_fputs()
{
    FILE *fp = fopen("text_puts.txt", "w");
    fputs("Hello world?\nOK\nHow are you", fp);//写入fp
    fclose(fp);
}
//从test.txt文件中读入一行文本(最多80个字符)并在屏幕上输出
void test_gets(){
    FILE *fp= fopen("text_gets.txt","r"); //打开文件
    char s[80]; //接收
    fgets(s,80,fp); ///*从fp读入80个字符到数组s*/
    printf("%s",s);
    fclose(fp);
}

/**
 * 读写一数值fread()和fwrite()
 *C语言用fread(…)和fwrite(…)函数读写文件一个数据，文件一般是二进制文件，数据可以是一个实型数据，也可以是一个结构体或数组数据等。需要注意的是，fwrite(…)是将内存指定单元数据原样(不进行任何转换)地存储到文件中，fread(…)是从文件中读取指定若干字节数据到内存指定单元中，也是不进行任何转换的，因此，这两个函数的读写一般是针对二进制文件，且文件一旦用fwrite(…)写，则该文件一般就只能用fread(…)进行读。
1.fread( )函数
格式：fread(buffer,size,count,fp);
功能：从fp指定文件中，读出每个数据由size个字节组成的count个数据，然后存入到buffer所指的单元中。若执行成功，则返回count值，否则返回NULL。
2.fwrite( )函数
格式：fwrite(buffer,size,count,fp);
功能：将buffer所指的每个数据由size个字节组成的count个数据写入fp指定文件中。若执行成功，则返回实际写入的数据个数，否则出错。
 */
// struct stu结构体数组中数据写入stu.dat文件中(TC或VC)。
#define Len sizeof(struct stu)
struct stu{
    int xh;
    char xm[20];
    char xb[4];
    float cj;
}; /*定义结构体数据类型*/

void test_fwrite(){
    struct stu s[2]={{1,"张三","男",89.5},{2,"李四","女",99.9}};
    FILE *fp= fopen("stu.dat","wb"); //dat txt(w)
    //FILE *fp=fopen("stu.txt","w"); //存储都是一样的 ，大小相同
    for(int i=0;i<2;i++){
        fwrite(&s[i],Len,sizeof(s)/Len,fp);
    }
    fclose(fp);

}
//从stu.dat数据文件(如上题创建的结果)中读入struct stu结构体数据并存到相应数组，最后从屏幕中显示相应内容(TC或VC)。(假设事先不知道数组元素个数，但知道最多10个)
void test_fread(){
    struct stu s[10]; int i=0,n;
    FILE *fp= fopen("stu.dat","rb"); //FILE *fp=fopen("stu.dat","r");
    n= fread(&s[0],Len,1,fp);
    while(n){
        printf("%d,%s,%s,%g\n",s[i].xh,s[i].xm,s[i].xb,s[i].cj);
        n= fread(&s[++i],Len,1,fp);
    }
    ///*若事先知道最多10个元素，可按如下方法。
    /*n= fread(s,Len,10,fp);
    for(i=0;i<10;i++){
        printf("%d,%s,%s,%g\n",s[i].xh,s[i].xm,s[i].xb,s[i].cj);
    }*/
    fclose(fp);
}

//12.5.1位置指针移到头函数rewind()
//文件中有一个位置指针，用于确定当前读写的位置。当读写一个文件，每读写完一个字符后，该位置指针自动移向下一个字符位置。如果有特殊需要，也可以通过相关函数强制位置指针指向指定位置。
//12.5.1位置指针移到头函数rewind()
//格式：void rewind(FILE *stream);
//功能：使stream所指定文件的位置指针重新返回文件的开头。函数无返回值。

void test_rewind(){
    FILE *f1,*f2;char c;
    f1= fopen("a1.txt","r");
    f2=fopen("a3.txt","w");
    while(!feof(f1)){
        c= getc(f1); //从f1读取
        if(c==EOF)break;
        putchar(c);
    }
    rewind(f1);
    while(!feof(f1)){
        c=getc(f1);
        if(c==EOF)break;
        putc(c,f2);
    }
    fclose(f1);
    fclose(f2);
    printf("\n");
}

//位置指针移到指定位置fseek()
/**
 * 格式：fseek(文件指针,位移量,起始点);
    功能：从起始点位置，按位移量对文件指针所指定文件的位置指针进行移位。函数执行成功，则返回0值，否则返回非0值。
    说明：
    (1)起始点：用SEEK_SET、SEEK_CUR、SEEK_END即0、1和2表示，0表示文件开始、1表示当前位置、2表示文件末尾。
    (2)位移量：以起始点为准，移动位移量个字节。
    (3)一般用于二进制文件操作。
 */
 // 用fgetc(…)和fputc(…)函数实现读文件text1.txt中末4个字节内容，并屏幕显示。
void test_fseek(){
     printf("-------------------------\n");
    FILE *f1,*f2;char c;
    f1= fopen("text_gets.txt","r");
    f2=stdout; //屏幕显示
     fseek(f1,-4,SEEK_END); //只读后面4个字节
     while(!feof(f1)){
         c= fgetc(f1);
         if(c==EOF)break;
         fputc(c,f2);//写入f2;
     }
     fclose(f1);
     fclose(f2);

}

void test_fseek_set(){
    printf("-------------------------\n");
    FILE *f1,*f2;char c;
    f1= fopen("text_gets.txt","r");
    f2=stdout; //屏幕显示
    fseek(f1,4,SEEK_SET); //只读后面4个字节
    while(!feof(f1)){
        c= fgetc(f1);
        if(c==EOF)break;
        fputc(c,f2);//写入f2;
    }
    fclose(f1);
    fclose(f2);

}

void test_fseek_cur(){
    printf("-------------------------\n");
    FILE *f1,*f2;char c;
    f1= fopen("text_gets.txt","r");
    f2=stdout; //屏幕显示

    while(!feof(f1)){
        c= fgetc(f1);
        if(c==EOF)break;
        fputc(c,f2);//写入f2;
        fseek(f1,1,SEEK_CUR); //每读一个字节文件指针向前移一个字节位置，即读一字节跳一字节
    }
    fclose(f1);
    fclose(f2);

}

/**
 * 主方法
 * @return
 */
int main24()
{
    setvbuf(stdout, NULL, _IONBF, 0); //解决debug时，scanf优于printf捕获的问题
    /*test_file_w();
    test_file_a();
    test_file_r_plus();*/
    //test_file_w_wb();
    //test_copy();
    test_copy_2_uppercase();
    // test_fscanf_fprintf_stdout();
    test_fputs();
    test_gets();
    //test_fwrite();
    test_fread();
    test_rewind();
    //todo 有点问题
    //test_fseek();
    //test_fseek_set();
    test_fseek_cur();
    return 0;
}

//将文件a1.txt中内容复制到a2.txt中，文件名通过命令行参数传递给main()函数，即实现DOS的COPY命令功能(如命令行：C001.exe a1.txt a2.txt)。
/*
void main(int argc,char *argv[]){ //传送参数
    FILE *f1,*f2;char c;
    if(argc<3) exit(0);
    f1= fopen(argv[1],"rb");
    f2= fopen(argv[2],"wb");
    while(!feof(f1)){ //判断读到文件结束
        c= fgetc(f1); //从f1读
        if(c==EOF)break;
        fputc(c,f2);
    }
    fclose(f1);
    fclose(f2);
}*/
